<!doctype html>
<html>
<head>
<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>

<link href='https://fonts.loli.net/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext' rel='stylesheet' type='text/css' /><style type='text/css'>html {overflow-x: initial !important;}:root { --bg-color:#ffffff; --text-color:#333333; --select-text-bg-color:#B5D6FC; --select-text-font-color:auto; --monospace:"Lucida Console",Consolas,"Courier",monospace; --title-bar-height:20px; }
.mac-os-11 { --title-bar-height:28px; }
html { font-size: 14px; background-color: var(--bg-color); color: var(--text-color); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; }
body { margin: 0px; padding: 0px; height: auto; bottom: 0px; top: 0px; left: 0px; right: 0px; font-size: 1rem; line-height: 1.42857; overflow-x: hidden; background: inherit; tab-size: 4; }
iframe { margin: auto; }
a.url { word-break: break-all; }
a:active, a:hover { outline: 0px; }
.in-text-selection, ::selection { text-shadow: none; background: var(--select-text-bg-color); color: var(--select-text-font-color); }
#write { margin: 0px auto; height: auto; width: inherit; word-break: normal; overflow-wrap: break-word; position: relative; white-space: normal; overflow-x: visible; padding-top: 36px; }
#write.first-line-indent p { text-indent: 2em; }
#write.first-line-indent li p, #write.first-line-indent p * { text-indent: 0px; }
#write.first-line-indent li { margin-left: 2em; }
.for-image #write { padding-left: 8px; padding-right: 8px; }
body.typora-export { padding-left: 30px; padding-right: 30px; }
.typora-export .footnote-line, .typora-export li, .typora-export p { white-space: pre-wrap; }
.typora-export .task-list-item input { pointer-events: none; }
@media screen and (max-width: 500px) {
  body.typora-export { padding-left: 0px; padding-right: 0px; }
  #write { padding-left: 20px; padding-right: 20px; }
  .CodeMirror-sizer { margin-left: 0px !important; }
  .CodeMirror-gutters { display: none !important; }
}
#write li > figure:last-child { margin-bottom: 0.5rem; }
#write ol, #write ul { position: relative; }
img { max-width: 100%; vertical-align: middle; image-orientation: from-image; }
button, input, select, textarea { color: inherit; font: inherit; }
input[type="checkbox"], input[type="radio"] { line-height: normal; padding: 0px; }
*, ::after, ::before { box-sizing: border-box; }
#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p, #write pre { width: inherit; }
#write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p { position: relative; }
p { line-height: inherit; }
h1, h2, h3, h4, h5, h6 { break-after: avoid-page; break-inside: avoid; orphans: 4; }
p { orphans: 4; }
h1 { font-size: 2rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.6rem; }
h4 { font-size: 1.4rem; }
h5 { font-size: 1.2rem; }
h6 { font-size: 1rem; }
.md-math-block, .md-rawblock, h1, h2, h3, h4, h5, h6, p { margin-top: 1rem; margin-bottom: 1rem; }
.hidden { display: none; }
.md-blockmeta { color: rgb(204, 204, 204); font-weight: 700; font-style: italic; }
a { cursor: pointer; }
sup.md-footnote { padding: 2px 4px; background-color: rgba(238, 238, 238, 0.7); color: rgb(85, 85, 85); border-radius: 4px; cursor: pointer; }
sup.md-footnote a, sup.md-footnote a:hover { color: inherit; text-transform: inherit; text-decoration: inherit; }
#write input[type="checkbox"] { cursor: pointer; width: inherit; height: inherit; }
figure { overflow-x: auto; margin: 1.2em 0px; max-width: calc(100% + 16px); padding: 0px; }
figure > table { margin: 0px; }
tr { break-inside: avoid; break-after: auto; }
thead { display: table-header-group; }
table { border-collapse: collapse; border-spacing: 0px; width: 100%; overflow: auto; break-inside: auto; text-align: left; }
table.md-table td { min-width: 32px; }
.CodeMirror-gutters { border-right: 0px; background-color: inherit; }
.CodeMirror-linenumber { user-select: none; }
.CodeMirror { text-align: left; }
.CodeMirror-placeholder { opacity: 0.3; }
.CodeMirror pre { padding: 0px 4px; }
.CodeMirror-lines { padding: 0px; }
div.hr:focus { cursor: none; }
#write pre { white-space: pre-wrap; }
#write.fences-no-line-wrapping pre { white-space: pre; }
#write pre.ty-contain-cm { white-space: normal; }
.CodeMirror-gutters { margin-right: 4px; }
.md-fences { font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; overflow: visible; white-space: pre; background: inherit; position: relative !important; }
.md-fences-adv-panel { width: 100%; margin-top: 10px; text-align: center; padding-top: 0px; padding-bottom: 8px; overflow-x: auto; }
#write .md-fences.mock-cm { white-space: pre-wrap; }
.md-fences.md-fences-with-lineno { padding-left: 0px; }
#write.fences-no-line-wrapping .md-fences.mock-cm { white-space: pre; overflow-x: auto; }
.md-fences.mock-cm.md-fences-with-lineno { padding-left: 8px; }
.CodeMirror-line, twitterwidget { break-inside: avoid; }
.footnotes { opacity: 0.8; font-size: 0.9rem; margin-top: 1em; margin-bottom: 1em; }
.footnotes + .footnotes { margin-top: 0px; }
.md-reset { margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: top; background: 0px 0px; text-decoration: none; text-shadow: none; float: none; position: static; width: auto; height: auto; white-space: nowrap; cursor: inherit; -webkit-tap-highlight-color: transparent; line-height: normal; font-weight: 400; text-align: left; box-sizing: content-box; direction: ltr; }
li div { padding-top: 0px; }
blockquote { margin: 1rem 0px; }
li .mathjax-block, li p { margin: 0.5rem 0px; }
li blockquote { margin: 1rem 0px; }
li { margin: 0px; position: relative; }
blockquote > :last-child { margin-bottom: 0px; }
blockquote > :first-child, li > :first-child { margin-top: 0px; }
.footnotes-area { color: rgb(136, 136, 136); margin-top: 0.714rem; padding-bottom: 0.143rem; white-space: normal; }
#write .footnote-line { white-space: pre-wrap; }
@media print {
  body, html { border: 1px solid transparent; height: 99%; break-after: avoid; break-before: avoid; font-variant-ligatures: no-common-ligatures; }
  #write { margin-top: 0px; padding-top: 0px; border-color: transparent !important; }
  .typora-export * { -webkit-print-color-adjust: exact; }
  .typora-export #write { break-after: avoid; }
  .typora-export #write::after { height: 0px; }
  .is-mac table { break-inside: avoid; }
  .typora-export-show-outline .typora-export-sidebar { display: none; }
}
.footnote-line { margin-top: 0.714em; font-size: 0.7em; }
a img, img a { cursor: pointer; }
pre.md-meta-block { font-size: 0.8rem; min-height: 0.8rem; white-space: pre-wrap; background: rgb(204, 204, 204); display: block; overflow-x: hidden; }
p > .md-image:only-child:not(.md-img-error) img, p > img:only-child { display: block; margin: auto; }
#write.first-line-indent p > .md-image:only-child:not(.md-img-error) img { left: -2em; position: relative; }
p > .md-image:only-child { display: inline-block; width: 100%; }
#write .MathJax_Display { margin: 0.8em 0px 0px; }
.md-math-block { width: 100%; }
.md-math-block:not(:empty)::after { display: none; }
.MathJax_ref { fill: currentcolor; }
[contenteditable="true"]:active, [contenteditable="true"]:focus, [contenteditable="false"]:active, [contenteditable="false"]:focus { outline: 0px; box-shadow: none; }
.md-task-list-item { position: relative; list-style-type: none; }
.task-list-item.md-task-list-item { padding-left: 0px; }
.md-task-list-item > input { position: absolute; top: 0px; left: 0px; margin-left: -1.2em; margin-top: calc(1em - 10px); border: none; }
.math { font-size: 1rem; }
.md-toc { min-height: 3.58rem; position: relative; font-size: 0.9rem; border-radius: 10px; }
.md-toc-content { position: relative; margin-left: 0px; }
.md-toc-content::after, .md-toc::after { display: none; }
.md-toc-item { display: block; color: rgb(65, 131, 196); }
.md-toc-item a { text-decoration: none; }
.md-toc-inner:hover { text-decoration: underline; }
.md-toc-inner { display: inline-block; cursor: pointer; }
.md-toc-h1 .md-toc-inner { margin-left: 0px; font-weight: 700; }
.md-toc-h2 .md-toc-inner { margin-left: 2em; }
.md-toc-h3 .md-toc-inner { margin-left: 4em; }
.md-toc-h4 .md-toc-inner { margin-left: 6em; }
.md-toc-h5 .md-toc-inner { margin-left: 8em; }
.md-toc-h6 .md-toc-inner { margin-left: 10em; }
@media screen and (max-width: 48em) {
  .md-toc-h3 .md-toc-inner { margin-left: 3.5em; }
  .md-toc-h4 .md-toc-inner { margin-left: 5em; }
  .md-toc-h5 .md-toc-inner { margin-left: 6.5em; }
  .md-toc-h6 .md-toc-inner { margin-left: 8em; }
}
a.md-toc-inner { font-size: inherit; font-style: inherit; font-weight: inherit; line-height: inherit; }
.footnote-line a:not(.reversefootnote) { color: inherit; }
.md-attr { display: none; }
.md-fn-count::after { content: "."; }
code, pre, samp, tt { font-family: var(--monospace); }
kbd { margin: 0px 0.1em; padding: 0.1em 0.6em; font-size: 0.8em; color: rgb(36, 39, 41); background: rgb(255, 255, 255); border: 1px solid rgb(173, 179, 185); border-radius: 3px; box-shadow: rgba(12, 13, 14, 0.2) 0px 1px 0px, rgb(255, 255, 255) 0px 0px 0px 2px inset; white-space: nowrap; vertical-align: middle; }
.md-comment { color: rgb(162, 127, 3); opacity: 0.8; font-family: var(--monospace); }
code { text-align: left; vertical-align: initial; }
a.md-print-anchor { white-space: pre !important; border-width: initial !important; border-style: none !important; border-color: initial !important; display: inline-block !important; position: absolute !important; width: 1px !important; right: 0px !important; outline: 0px !important; background: 0px 0px !important; text-decoration: initial !important; text-shadow: initial !important; }
.md-inline-math .MathJax_SVG .noError { display: none !important; }
.html-for-mac .inline-math-svg .MathJax_SVG { vertical-align: 0.2px; }
.md-fences-math .MathJax_SVG_Display, .md-math-block .MathJax_SVG_Display { text-align: center; margin: 0px; position: relative; text-indent: 0px; max-width: none; max-height: none; min-height: 0px; min-width: 100%; width: auto; overflow-y: visible; display: block !important; }
.MathJax_SVG_Display, .md-inline-math .MathJax_SVG_Display { width: auto; margin: inherit; display: inline-block !important; }
.MathJax_SVG .MJX-monospace { font-family: var(--monospace); }
.MathJax_SVG .MJX-sans-serif { font-family: sans-serif; }
.MathJax_SVG { display: inline; font-style: normal; font-weight: 400; line-height: normal; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; overflow-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; zoom: 90%; }
#math-inline-preview-content { zoom: 1.1; }
.MathJax_SVG * { transition: none 0s ease 0s; }
.MathJax_SVG_Display svg { vertical-align: middle !important; margin-bottom: 0px !important; margin-top: 0px !important; }
.os-windows.monocolor-emoji .md-emoji { font-family: "Segoe UI Symbol", sans-serif; }
.md-diagram-panel > svg { max-width: 100%; }
[lang="flow"] svg, [lang="mermaid"] svg { max-width: 100%; height: auto; }
[lang="mermaid"] .node text { font-size: 1rem; }
table tr th { border-bottom: 0px; }
video { max-width: 100%; display: block; margin: 0px auto; }
iframe { max-width: 100%; width: 100%; border: none; }
.highlight td, .highlight tr { border: 0px; }
mark { background: rgb(255, 255, 0); color: rgb(0, 0, 0); }
.md-html-inline .md-plain, .md-html-inline strong, mark .md-inline-math, mark strong { color: inherit; }
.md-expand mark .md-meta { opacity: 0.3 !important; }
mark .md-meta { color: rgb(0, 0, 0); }
@media print {
  .typora-export h1, .typora-export h2, .typora-export h3, .typora-export h4, .typora-export h5, .typora-export h6 { break-inside: avoid; }
}
.md-diagram-panel .messageText { stroke: none !important; }
.md-diagram-panel .start-state { fill: var(--node-fill); }
.md-diagram-panel .edgeLabel rect { opacity: 1 !important; }
.md-require-zoom-fix foreignobject { font-size: var(--mermaid-font-zoom); }
.md-fences.md-fences-math { font-size: 1em; }
.md-fences-math .MathJax_SVG_Display { margin-top: 8px; cursor: default; }
.md-fences-advanced:not(.md-focus) { padding: 0px; white-space: nowrap; border: 0px; }
.md-fences-advanced:not(.md-focus) { background: inherit; }
.typora-export-show-outline .typora-export-content { max-width: 1440px; margin: auto; display: flex; flex-direction: row; }
.typora-export-sidebar { width: 300px; font-size: 0.8rem; margin-top: 80px; margin-right: 18px; }
.typora-export-show-outline #write { --webkit-flex:2; flex: 2 1 0%; }
.typora-export-sidebar .outline-content { position: fixed; top: 0px; max-height: 100%; overflow: hidden auto; padding-bottom: 30px; padding-top: 60px; width: 300px; }
@media screen and (max-width: 1024px) {
  .typora-export-sidebar, .typora-export-sidebar .outline-content { width: 240px; }
}
@media screen and (max-width: 800px) {
  .typora-export-sidebar { display: none; }
}
.outline-content li, .outline-content ul { margin-left: 0px; margin-right: 0px; padding-left: 0px; padding-right: 0px; list-style: none; }
.outline-content ul { margin-top: 0px; margin-bottom: 0px; }
.outline-content strong { font-weight: 400; }
.outline-expander { width: 1rem; height: 1.42857rem; position: relative; display: table-cell; vertical-align: middle; cursor: pointer; padding-left: 4px; }
.outline-expander::before { content: ""; position: relative; font-family: Ionicons; display: inline-block; font-size: 8px; vertical-align: middle; }
.outline-item { padding-top: 3px; padding-bottom: 3px; cursor: pointer; }
.outline-expander:hover::before { content: ""; }
.outline-h1 > .outline-item { padding-left: 0px; }
.outline-h2 > .outline-item { padding-left: 1em; }
.outline-h3 > .outline-item { padding-left: 2em; }
.outline-h4 > .outline-item { padding-left: 3em; }
.outline-h5 > .outline-item { padding-left: 4em; }
.outline-h6 > .outline-item { padding-left: 5em; }
.outline-label { cursor: pointer; display: table-cell; vertical-align: middle; text-decoration: none; color: inherit; }
.outline-label:hover { text-decoration: underline; }
.outline-item:hover { border-color: rgb(245, 245, 245); background-color: var(--item-hover-bg-color); }
.outline-item:hover { margin-left: -28px; margin-right: -28px; border-left: 28px solid transparent; border-right: 28px solid transparent; }
.outline-item-single .outline-expander::before, .outline-item-single .outline-expander:hover::before { display: none; }
.outline-item-open > .outline-item > .outline-expander::before { content: ""; }
.outline-children { display: none; }
.info-panel-tab-wrapper { display: none; }
.outline-item-open > .outline-children { display: block; }
.typora-export .outline-item { padding-top: 1px; padding-bottom: 1px; }
.typora-export .outline-item:hover { margin-right: -8px; border-right: 8px solid transparent; }
.typora-export .outline-expander::before { content: "+"; font-family: inherit; top: -1px; }
.typora-export .outline-expander:hover::before, .typora-export .outline-item-open > .outline-item > .outline-expander::before { content: "−"; }
.typora-export-collapse-outline .outline-children { display: none; }
.typora-export-collapse-outline .outline-item-open > .outline-children, .typora-export-no-collapse-outline .outline-children { display: block; }
.typora-export-no-collapse-outline .outline-expander::before { content: "" !important; }
.typora-export-show-outline .outline-item-active > .outline-item .outline-label { font-weight: 700; }


.CodeMirror { height: auto; }
.CodeMirror.cm-s-inner { background: inherit; }
.CodeMirror-scroll { overflow: auto hidden; z-index: 3; }
.CodeMirror-gutter-filler, .CodeMirror-scrollbar-filler { background-color: rgb(255, 255, 255); }
.CodeMirror-gutters { border-right: 1px solid rgb(221, 221, 221); background: inherit; white-space: nowrap; }
.CodeMirror-linenumber { padding: 0px 3px 0px 5px; text-align: right; color: rgb(153, 153, 153); }
.cm-s-inner .cm-keyword { color: rgb(119, 0, 136); }
.cm-s-inner .cm-atom, .cm-s-inner.cm-atom { color: rgb(34, 17, 153); }
.cm-s-inner .cm-number { color: rgb(17, 102, 68); }
.cm-s-inner .cm-def { color: rgb(0, 0, 255); }
.cm-s-inner .cm-variable { color: rgb(0, 0, 0); }
.cm-s-inner .cm-variable-2 { color: rgb(0, 85, 170); }
.cm-s-inner .cm-variable-3 { color: rgb(0, 136, 85); }
.cm-s-inner .cm-string { color: rgb(170, 17, 17); }
.cm-s-inner .cm-property { color: rgb(0, 0, 0); }
.cm-s-inner .cm-operator { color: rgb(152, 26, 26); }
.cm-s-inner .cm-comment, .cm-s-inner.cm-comment { color: rgb(170, 85, 0); }
.cm-s-inner .cm-string-2 { color: rgb(255, 85, 0); }
.cm-s-inner .cm-meta { color: rgb(85, 85, 85); }
.cm-s-inner .cm-qualifier { color: rgb(85, 85, 85); }
.cm-s-inner .cm-builtin { color: rgb(51, 0, 170); }
.cm-s-inner .cm-bracket { color: rgb(153, 153, 119); }
.cm-s-inner .cm-tag { color: rgb(17, 119, 0); }
.cm-s-inner .cm-attribute { color: rgb(0, 0, 204); }
.cm-s-inner .cm-header, .cm-s-inner.cm-header { color: rgb(0, 0, 255); }
.cm-s-inner .cm-quote, .cm-s-inner.cm-quote { color: rgb(0, 153, 0); }
.cm-s-inner .cm-hr, .cm-s-inner.cm-hr { color: rgb(153, 153, 153); }
.cm-s-inner .cm-link, .cm-s-inner.cm-link { color: rgb(0, 0, 204); }
.cm-negative { color: rgb(221, 68, 68); }
.cm-positive { color: rgb(34, 153, 34); }
.cm-header, .cm-strong { font-weight: 700; }
.cm-del { text-decoration: line-through; }
.cm-em { font-style: italic; }
.cm-link { text-decoration: underline; }
.cm-error { color: red; }
.cm-invalidchar { color: red; }
.cm-constant { color: rgb(38, 139, 210); }
.cm-defined { color: rgb(181, 137, 0); }
div.CodeMirror span.CodeMirror-matchingbracket { color: rgb(0, 255, 0); }
div.CodeMirror span.CodeMirror-nonmatchingbracket { color: rgb(255, 34, 34); }
.cm-s-inner .CodeMirror-activeline-background { background: inherit; }
.CodeMirror { position: relative; overflow: hidden; }
.CodeMirror-scroll { height: 100%; outline: 0px; position: relative; box-sizing: content-box; background: inherit; }
.CodeMirror-sizer { position: relative; }
.CodeMirror-gutter-filler, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-vscrollbar { position: absolute; z-index: 6; display: none; }
.CodeMirror-vscrollbar { right: 0px; top: 0px; overflow: hidden; }
.CodeMirror-hscrollbar { bottom: 0px; left: 0px; overflow: hidden; }
.CodeMirror-scrollbar-filler { right: 0px; bottom: 0px; }
.CodeMirror-gutter-filler { left: 0px; bottom: 0px; }
.CodeMirror-gutters { position: absolute; left: 0px; top: 0px; padding-bottom: 30px; z-index: 3; }
.CodeMirror-gutter { white-space: normal; height: 100%; box-sizing: content-box; padding-bottom: 30px; margin-bottom: -32px; display: inline-block; }
.CodeMirror-gutter-wrapper { position: absolute; z-index: 4; background: 0px 0px !important; border: none !important; }
.CodeMirror-gutter-background { position: absolute; top: 0px; bottom: 0px; z-index: 4; }
.CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; }
.CodeMirror-lines { cursor: text; }
.CodeMirror pre { border-radius: 0px; border-width: 0px; background: 0px 0px; font-family: inherit; font-size: inherit; margin: 0px; white-space: pre; overflow-wrap: normal; color: inherit; z-index: 2; position: relative; overflow: visible; }
.CodeMirror-wrap pre { overflow-wrap: break-word; white-space: pre-wrap; word-break: normal; }
.CodeMirror-code pre { border-right: 30px solid transparent; width: fit-content; }
.CodeMirror-wrap .CodeMirror-code pre { border-right: none; width: auto; }
.CodeMirror-linebackground { position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 0; }
.CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto; }
.CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; }
.CodeMirror-measure { position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden; }
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor { position: absolute; visibility: hidden; border-right: none; width: 0px; }
.CodeMirror div.CodeMirror-cursor { visibility: hidden; }
.CodeMirror-focused div.CodeMirror-cursor { visibility: inherit; }
.cm-searching { background: rgba(255, 255, 0, 0.4); }
span.cm-underlined { text-decoration: underline; }
span.cm-strikethrough { text-decoration: line-through; }
.cm-tw-syntaxerror { color: rgb(255, 255, 255); background-color: rgb(153, 0, 0); }
.cm-tw-deleted { text-decoration: line-through; }
.cm-tw-header5 { font-weight: 700; }
.cm-tw-listitem:first-child { padding-left: 10px; }
.cm-tw-box { border-style: solid; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-color: inherit; border-top-width: 0px !important; }
.cm-tw-underline { text-decoration: underline; }
@media print {
  .CodeMirror div.CodeMirror-cursor { visibility: hidden; }
}


:root {
    --side-bar-bg-color: #fafafa;
    --control-text-color: #777;
}

@include-when-export url(https://fonts.loli.net/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);

/* open-sans-regular - latin-ext_latin */
  /* open-sans-italic - latin-ext_latin */
    /* open-sans-700 - latin-ext_latin */
    /* open-sans-700italic - latin-ext_latin */
  html {
    font-size: 16px;
}

body {
    font-family: "Open Sans","Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
    color: rgb(51, 51, 51);
    line-height: 1.6;
}

#write {
    max-width: 860px;
  	margin: 0 auto;
  	padding: 30px;
    padding-bottom: 100px;
}

@media only screen and (min-width: 1400px) {
	#write {
		max-width: 1024px;
	}
}

@media only screen and (min-width: 1800px) {
	#write {
		max-width: 1200px;
	}
}

#write > ul:first-child,
#write > ol:first-child{
    margin-top: 30px;
}

a {
    color: #4183C4;
}
h1,
h2,
h3,
h4,
h5,
h6 {
    position: relative;
    margin-top: 1rem;
    margin-bottom: 1rem;
    font-weight: bold;
    line-height: 1.4;
    cursor: text;
}
h1:hover a.anchor,
h2:hover a.anchor,
h3:hover a.anchor,
h4:hover a.anchor,
h5:hover a.anchor,
h6:hover a.anchor {
    text-decoration: none;
}
h1 tt,
h1 code {
    font-size: inherit;
}
h2 tt,
h2 code {
    font-size: inherit;
}
h3 tt,
h3 code {
    font-size: inherit;
}
h4 tt,
h4 code {
    font-size: inherit;
}
h5 tt,
h5 code {
    font-size: inherit;
}
h6 tt,
h6 code {
    font-size: inherit;
}
h1 {
    font-size: 2.25em;
    line-height: 1.2;
    border-bottom: 1px solid #eee;
}
h2 {
    font-size: 1.75em;
    line-height: 1.225;
    border-bottom: 1px solid #eee;
}

/*@media print {
    .typora-export h1,
    .typora-export h2 {
        border-bottom: none;
        padding-bottom: initial;
    }

    .typora-export h1::after,
    .typora-export h2::after {
        content: "";
        display: block;
        height: 100px;
        margin-top: -96px;
        border-top: 1px solid #eee;
    }
}*/

h3 {
    font-size: 1.5em;
    line-height: 1.43;
}
h4 {
    font-size: 1.25em;
}
h5 {
    font-size: 1em;
}
h6 {
   font-size: 1em;
    color: #777;
}
p,
blockquote,
ul,
ol,
dl,
table{
    margin: 0.8em 0;
}
li>ol,
li>ul {
    margin: 0 0;
}
hr {
    height: 2px;
    padding: 0;
    margin: 16px 0;
    background-color: #e7e7e7;
    border: 0 none;
    overflow: hidden;
    box-sizing: content-box;
}

li p.first {
    display: inline-block;
}
ul,
ol {
    padding-left: 30px;
}
ul:first-child,
ol:first-child {
    margin-top: 0;
}
ul:last-child,
ol:last-child {
    margin-bottom: 0;
}
blockquote {
    border-left: 4px solid #dfe2e5;
    padding: 0 15px;
    color: #777777;
}
blockquote blockquote {
    padding-right: 0;
}
table {
    padding: 0;
    word-break: initial;
}
table tr {
    border: 1px solid #dfe2e5;
    margin: 0;
    padding: 0;
}
table tr:nth-child(2n),
thead {
    background-color: #f8f8f8;
}
table th {
    font-weight: bold;
    border: 1px solid #dfe2e5;
    border-bottom: 0;
    margin: 0;
    padding: 6px 13px;
}
table td {
    border: 1px solid #dfe2e5;
    margin: 0;
    padding: 6px 13px;
}
table th:first-child,
table td:first-child {
    margin-top: 0;
}
table th:last-child,
table td:last-child {
    margin-bottom: 0;
}

.CodeMirror-lines {
    padding-left: 4px;
}

.code-tooltip {
    box-shadow: 0 1px 1px 0 rgba(0,28,36,.3);
    border-top: 1px solid #eef2f2;
}

.md-fences,
code,
tt {
    border: 1px solid #e7eaed;
    background-color: #f8f8f8;
    border-radius: 3px;
    padding: 0;
    padding: 2px 4px 0px 4px;
    font-size: 0.9em;
}

code {
    background-color: #f3f4f4;
    padding: 0 2px 0 2px;
}

.md-fences {
    margin-bottom: 15px;
    margin-top: 15px;
    padding-top: 8px;
    padding-bottom: 6px;
}


.md-task-list-item > input {
  margin-left: -1.3em;
}

@media print {
    html {
        font-size: 13px;
    }
    table,
    pre {
        page-break-inside: avoid;
    }
    pre {
        word-wrap: break-word;
    }
}

.md-fences {
	background-color: #f8f8f8;
}
#write pre.md-meta-block {
	padding: 1rem;
    font-size: 85%;
    line-height: 1.45;
    background-color: #f7f7f7;
    border: 0;
    border-radius: 3px;
    color: #777777;
    margin-top: 0 !important;
}

.mathjax-block>.code-tooltip {
	bottom: .375rem;
}

.md-mathjax-midline {
    background: #fafafa;
}

#write>h3.md-focus:before{
	left: -1.5625rem;
	top: .375rem;
}
#write>h4.md-focus:before{
	left: -1.5625rem;
	top: .285714286rem;
}
#write>h5.md-focus:before{
	left: -1.5625rem;
	top: .285714286rem;
}
#write>h6.md-focus:before{
	left: -1.5625rem;
	top: .285714286rem;
}
.md-image>.md-meta {
    /*border: 1px solid #ddd;*/
    border-radius: 3px;
    padding: 2px 0px 0px 4px;
    font-size: 0.9em;
    color: inherit;
}

.md-tag {
    color: #a7a7a7;
    opacity: 1;
}

.md-toc { 
    margin-top:20px;
    padding-bottom:20px;
}

.sidebar-tabs {
    border-bottom: none;
}

#typora-quick-open {
    border: 1px solid #ddd;
    background-color: #f8f8f8;
}

#typora-quick-open-item {
    background-color: #FAFAFA;
    border-color: #FEFEFE #e5e5e5 #e5e5e5 #eee;
    border-style: solid;
    border-width: 1px;
}

/** focus mode */
.on-focus-mode blockquote {
    border-left-color: rgba(85, 85, 85, 0.12);
}

header, .context-menu, .megamenu-content, footer{
    font-family: "Segoe UI", "Arial", sans-serif;
}

.file-node-content:hover .file-node-icon,
.file-node-content:hover .file-node-open-state{
    visibility: visible;
}

.mac-seamless-mode #typora-sidebar {
    background-color: #fafafa;
    background-color: var(--side-bar-bg-color);
}

.md-lang {
    color: #b4654d;
}

/*.html-for-mac {
    --item-hover-bg-color: #E6F0FE;
}*/

#md-notification .btn {
    border: 0;
}

.dropdown-menu .divider {
    border-color: #e5e5e5;
    opacity: 0.4;
}

.ty-preferences .window-content {
    background-color: #fafafa;
}

.ty-preferences .nav-group-item.active {
    color: white;
    background: #999;
}

.menu-item-container a.menu-style-btn {
    background-color: #f5f8fa;
    background-image: linear-gradient( 180deg , hsla(0, 0%, 100%, 0.8), hsla(0, 0%, 100%, 0)); 
}


 :root {--mermaid-font-zoom:1.25em ;} 
</style><title>技术开发规范</title>
</head>
<body class='typora-export os-windows'><div class='typora-export-content'>
<div id='write'  class=''><div>
    <p align="right"><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><center><strong><font size="50">项目开发规范</font></strong></center><h2>
    <center><font color="#999">Version 1.1.2</font></center>
</h2><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><p><img src="assets/images/manual/400x400.jpg" referrerpolicy="no-referrer" alt="300x300"></p><p>&nbsp;</p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><p>&nbsp;</p><p><strong id="目录"><span>目录：</span></strong></p><ol>
    <li style="line-height:2.5"><a href="#前言">前言</a></li>
    <li style="line-height:2.5"><a href="#过于灵活是一件糟糕的事情">《过于灵活是一件糟糕的事情》</a></li>
    <li style="line-height:2.5"><a href="#关于「能愿动词」">关于「能愿动词」</a></li>
    <li style="line-height:2.5"><a href="#开发哲学">开发哲学</a></li>
    <li style="line-height:2.5"><a href="#需求分析">需求分析</a></li>
    <li style="line-height:2.5"><a href="#版本选择">版本选择</a></li>
    <li style="line-height:2.5"><a href="#测试环境和生产环境">测试环境和生产环境</a></li>
    <li style="line-height:2.5"><a href="#静态资源">静态资源</a></li>
    <li style="line-height:2.5"><a href="#版本管理">版本管理</a></li>
    <li style="line-height:2.5"><a href="#开发约定">开发约定</a></li>
    <li style="line-height:2.5"><a href="#API文档规范">API文档规范</a></li>
    <li style="line-height:2.5"><a href="#前端编码规范">前端编码规范</a></li>
    <li style="line-height:2.5"><a href="#后端编码规范">后端编码规范</a></li>
    <li style="line-height:2.5"><a href="#二次开发项目">二次开发项目</a></li>
    <li style="line-height:2.5"><a href="#测试反馈">测试反馈</a></li>
    <li style="line-height:2.5"><a href="#安全实践">安全实践</a></li>
    <li style="line-height:2.5"><a href="#推荐阅读">推荐阅读</a></li>
</ol><p>&nbsp;</p><p>&nbsp;</p><p>&nbsp;</p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div>
<h3 id="前言"><center><strong>前言</strong></center></h3><hr><p><span>因为篇幅原因本规范无法涉及到项目里每一块代码的编写标准，所以此处重点说明下此规范遵循的 「</span><a href="#开发哲学"><span>开发哲学</span></a><span>」，开发中请把其当做指明灯，来指引你做决策。</span></p><p><span>这套规范有以下优点：</span></p><ul><li><span>高效编码 - 避免了过多的选择造成的『决策时间』浪费；</span></li><li><span>风格统一 - 最大程度统一了开发团队成员代码书写风格和思路；</span></li><li><span>减少错误 - 减小初级工程师的犯错几率。</span></li></ul><div style="page-break-after: always;"></div><div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="过于灵活是一件糟糕的事情"><center>《过于灵活是一件糟糕的事情》</center></h2><hr><p>&nbsp;</p><p><span>我们在学校时，老师教授我们一个问题可以有多种解决方法；在开发中，一个问题网络上也会有多种解决方案；在个人开发项目时，通常也会凭自己的喜好来解决问题。但是对于团队的协同开发来说，大部分时候，太多的选择反而是累赘。</span></p><p><span>在开发过程我们都曾或多或少都思考过以下问题：</span></p><p><span>					</span><span>1. </span><code>Example.php</code><span> 文件放在哪个文件夹合适呢？</span></p><p><span>					</span><span>2. 方法应该怎么命名呢？</span></p><p><span>					</span><span>3. 我放在这里是对的吗？</span></p><p><span>					</span><span>4. 大家都是怎么做的呢？</span></p><p><span>上面的问题，如果你独自开发个人项目倒是问题不大，最多一两年后回来看你的项目需要浪费点时间熟悉而已。然而，如果是在一个中大型的商业项目开发中，团队中有着几个甚至十几个开发者，没有规范的情况下，开发者会根据各自的喜好去选择，有时甚至出现一个开发者尝试多个选项的可能，就会造成整个团队产出的代码可读性极低，代码结构混乱，也为后面的项目代码的维护带来了难度。</span></p><p><span>与其无休止的争论哪种选项最好，还不如只知道一种选项 。 这一种选项能覆盖大部分的用例，且兼备开发效率、程序执行效率、扩展性、安全性等最佳实践，当再次遇到此类需求时，毫不犹豫地使用这一种选项直接了当地解决问题。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div>
<h2 id="关于「能愿动词」"><center>关于「能愿动词」</center></h2><hr /><p><span>为了避免歧义，文档大量使用了「能愿动词」，对应的解释如下：</span></p><ol start='' ><li><span style="background:#fff9a0"><span>必须（Must） </span><span>			</span><span>              - 只能这样子做，请无条件遵循，没有别的选项；  </span></span></li><li><span style="background:#fff9a0"><span>绝不（Must Not）                   - 严令禁止，在任何情况下都不能这样做；</span></span></li><li><span style="background:#fff9a0"><span>应该（Should）                       - 强烈建议这样做，但是不强求；</span></span></li><li><span style="background:#fff9a0"><span>不应该（Should Not）           - 强烈建议不这样做，但是不强求；</span></span></li><li><span style="background:#fff9a0"><span>可以（May）                            - 选择性高一点，在这个文档内，此词语使用较少；  </span></span></li></ol><blockquote><p><span>文献参考  </span><a href='http://www.ietf.org/rfc/rfc2119.txt'><span>RFC 2119</span></a></p></blockquote><h3 id='关于执行'><strong><span>关于执行</span></strong></h3><hr /><p><span>在这份规范里，有些内容里会解释『这样做的理由』，这样做的目的是为了达成共识。</span></p><blockquote><p><span>请不要以此『理由』的准确性来怀疑规范的权威性，规范就是规范，可以讨论改正，但在执行的时候 </span><strong><span>必须</span></strong><span> 严格遵守。</span></p></blockquote><p><span>请把「团队项目开发」想象就是在行军打仗，对于规范要绝对服从。要有大局观，做到团结一致，把个人的喜好放一边，把整个团队的执行效率放在第一位。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><p>&nbsp;</p><h2 id="开发哲学"><center>开发哲学</center></h2><hr /><ul><li style="line-height:2.5">DRY –「Don’t Repeat Yourself」不写重复的逻辑代码；</li><li style="line-height:2.5">KISS - 「Keep it Simple, Stupid」提倡简单易读的代码，不写高深、晦涩难懂的代码，不过度设计；</li>
    <li style="line-height:2.5">约定俗成 - 优先选择框架提倡的做法，不过度配置；</li><li style="line-height:2.5">主厨精选 - 让有经验的人来为你选择方案，不独创方案；</li>
<li style="line-height:2.5">不造轮子 - 不要去重复解决已经有成熟解决方案的问题，你应该直接采用它;</li><li style="line-height:2.5">官方提倡 - 优先选择官方推崇的方案。</li></ul><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="需求分析"><center>需求分析</center></h2><hr>
<h3><strong>开发准备</strong></h3><hr><p><span>在一个项目开始之前，相关开发人员需要明确项目需求，产品 </span><strong><span>必须</span></strong><span> 提供原型图，描述业务场景与逻辑需求。</span></p><p><span>在项目开发过程中，所有的需求，以及添加或修改的功能变动，</span><strong><span>必须</span></strong><span> 落实到文本形式并保留记录，口头说明是没有意义的。对于 </span><strong><span>新增需求</span></strong><span> 如果当前已经有开发任务，需要重新排算工期。</span></p><p><strong><span>越是复杂的业务逻辑，越需要严谨需求文档</span></strong></p><p><span>明确目标是非常重要的，如果一开始就做错了，做再多都是无用的。 </span></p><p>&nbsp;</p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p><h2 id="版本选择"><center>框架选择</center></h2></div><p>&nbsp;</p><hr /><p><span>如今我们都习惯使用 </span><code>框架</code><span> + </span><code>组件</code><span> 模式开发，一个好的框架能让开发效率事半功倍。</span></p><p><strong><span>在选择开发框架时，应该考虑以下几个问题：</span></strong></p><ol start='' ><li><span>框架作者是否在积极维护它，最近一次更新 </span><strong><span>应该</span></strong><span> 在半年左右，1年以上 </span><strong><span>应该</span></strong><span> 谨慎选择，2年以上的 </span><strong><span>应该</span></strong><span> 避免使用，积极的更新意味着作者重视它，并且经年累月，已经积累了很多用户，你在使用这个框架时遇到的问题都可以在 </span><code>git</code><span> 上面找到相关的 </span><code>issue</code><span>。</span></li><li><span>网络关于它的贴文是否很多？以 </span><code>vue</code><span> 框架举例，如果你在使用它的时候，遇到了报错，把错误信息复制到 </span><code>百度</code><span> 和 </span><code>谷歌</code><span>，都能轻易找到关于这个错误的讨论，不限于是国内的 </span><code>CSDN</code><span> 还是国外的 </span><code>stackoverflow</code><span> 等。</span></li><li><span>遇到疑难杂症是否能解决？假如你在使用框架的过程中遇到的问题无论网络还是作者的都不能明确的给出你回答，你是否能 </span><code>hold</code><span> 的住？如果你不能，你的工作伙伴是否能？如果都不能，那就 </span><strong><span>不应该</span></strong><span> 使用它。</span></li></ol><p><span>选择框架版本时， </span><strong><span>应该</span></strong><span> 优先考虑 </span><code>LTS</code><span> 版本或「一般发行版」，因为安全性和稳定性考虑，商业项目开发中 </span><strong><span>绝不</span></strong><span> 使用 </span><code>beta</code><span> 版本。</span></p><h3 id='什么是lts'><strong><span>什么是LTS</span></strong></h3><hr /><p><span>首先，你需要明白LTS的概念。</span></p><blockquote><p><span>长期支持 （英语：Long-term support，缩写：LTS）是一种软件的产品生命周期政策，特别是开源软件，它增加了软件开发过程及软件版本周期的可靠度。长期支持延长了软件维护的周期；它也改变了软件更新（补丁）的类型及频率以降低风险、费用及软件部署的中断时间，同时提升了软件的可靠性。但这并不必然包含技术支持。</span></p><p><span>在长期支持周期的开始，软件设计师会将软件特性冻结：他们制作补丁来修复程序错误及计算机安全隐患，但不会加入新的，可能会造成软件破坏的功能。软件维护者可能会单独发布补丁，或是将其置于维护版本、小数点版本或是服务包中发布。支持周期结束后，其称之为产品的生命周期结束。</span></p><p><span>“长期支持” 这个术语通常是保留给特殊的软件版本，其他版本会有更短的生命周期。通常来说，长期支持版本至少会被维护两年。</span></p><p><span>来自 </span><a href='https://zh.wikipedia.org/w/index.php?title=長期支援&amp;redirect=no'><span>维基百科</span></a></p></blockquote><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><p>&nbsp;</p><h2 id="测试环境和生产环境"><center>测试环境和生产环境</center></h2><hr /><h4 id='环境说明'><span>环境说明：</span></h4><hr /><p><span>一般情况下，一个项目 </span><strong><span>应该</span></strong><span> 有以下三个基本的项目环境：</span></p><ul><li><span>Local - 开发环境</span></li><li><span>Staging - 线上测试环境</span></li><li><span>Production - 线上生产环境</span></li></ul><h4 id='使用软件'><span>使用软件：</span></h4><hr /><ul><li><p><strong><span>服务器系统</span></strong></p><p><span>我们团队后端都是 PHP 语言，服务器系统 </span><strong><span>应该</span></strong><span> 优先考虑 Ubuntu 并且是 LTS 支持的系统。如 Ubuntu 18.04.5 LTS 或者 Ubuntu 20.04.2 LTS 。</span></p></li><li><p><strong><span>PHP 7</span></strong></p><p><span>PHP 版本 </span><strong><span>应该</span></strong><span> 优先考虑 PHP 7，不止因为其运行高效，还因为随着 PHP 7 的广泛应用，PHP 7 以下的版本将会很快停止维护。</span></p></li><li><p><strong><span>MYSQL 5.7</span></strong></p><p><span>数据库软件 </span><strong><span>应该</span></strong><span> 优先选择 MySQL，因为其使用率最高。MySQL 5.7 与 PHP 7 一样，已经是大势所趋，选择版本时 </span><strong><span>应该</span></strong><span> 优先考虑 MySQL 5.7。</span></p></li><li><p><strong><span>其他软件</span></strong></p><p><strong><span>应该</span></strong><span> 优先选择 </span><strong><span>流行</span></strong><span> </span><strong><span>稳定</span></strong><span> 版本。线上环境 </span><strong><span>绝不</span></strong><span> 使用 Beta 或者其他不稳定发行版。</span></p></li></ul><h4 id='production---线上生产环境'><span>Production - 线上生产环境</span></h4><hr /><p><span>在生产环境中，</span><strong><span>不应该</span></strong><span> 安装 </span><code>composer</code><span> 中的 </span><code>dev</code><span> 扩展包，</span><strong><span>绝不</span></strong><span> 开启框架 </span><code>DEBUG</code><span> 模式， </span><strong><span>绝不</span></strong><span> 在生产环境进行调试。</span></p><hr /><h4 id='staging---线上测试环境'><span>Staging - 线上测试环境</span></h4><hr /><p><span>除了域名等其他独立应用配置以外，环境 </span><strong><span>必须</span></strong><span> 跟 Production 保持高度一致性，可以的话 </span><strong><span>应该</span></strong><span> 与 Production 使用同台服务器。</span></p><p><span>开发专用扩展包 </span><strong><span>必须</span></strong><span> 使用 </span><code>--dev</code><span> 参数</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">composer</span> <span class="cm-keyword">require</span> <span class="cm-variable">laracasts</span><span class="cm-operator">/</span><span class="cm-variable">generators</span> <span class="cm-operator">--</span><span class="cm-variable">dev</span></span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>测试和生产环境都 </span><strong><span>必须</span></strong><span> 配置 HTTPS。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><p>&nbsp;</p><h2 id="静态资源"><center>静态资源</center></h2><hr /><h2 id='说明'><span>说明</span></h2><hr /><p><span>一般我们将 css, js, font, 图片等称为静态资源。</span></p><p><span>如无特殊要求，图片资源应该尽量使用 </span><code>.jpg</code><span> 格式，有透明需求才使用 </span><code>.png</code><span> 格式，存储图片应该使用 </span><code>Web存储格式</code><span>，单张素材资源控制在 200kb 以内， Banner 类似的产品图大小也应该控制在 1MB 以内，如果超出大小，应该切成多张上传处理</span></p><h2 id='第三方cdn'><span>第三方CDN</span></h2><hr /><p><span style="background:#fff9a0"><span>项目中使用了第三方前端类库中的 CSS/JS/FONTS 等静态资源，</span><strong><span>绝不</span></strong><span> 使用第三方链接进行加载（包括第三方 CDN）。</span></span></p><p><span style="background:#fff9a0"><span>第三方类库静态资源 </span><strong><span>必须</span></strong><span> 下载并纳入代码版本管理器中。</span></span></p><p><img src="assets/images/manual/r.png" alt="2" style="zoom:60%;" /></p><h2 id='行内样式与js'><span>行内样式与JS</span></h2><hr /><p><span>CSS 和 JS </span><strong><span>应该</span></strong><span> 使用独立的文件进行加载， </span><strong><span>不应该</span></strong><span> 直接写在页面中。          </span></p><p><img src="assets/images/manual/r2.png" style="zoom:70%;" /></p><hr /><h2 id='控制数量'><span>控制数量</span></h2><p><span>使用 VUE 开发作为前端时，应该遵循以下要求：</span></p><p><span>页面 CSS 文件加载数量 </span><strong><span>应该</span></strong><span> 不超过 2 个，</span><strong><span>绝不</span></strong><span> 超过 3 个。</span></p><p><span>页面 JS 文件加载数量 </span><strong><span>应该</span></strong><span> 不超过 2 个，</span><strong><span>绝不</span></strong><span> 超过 3 个。</span></p><p><code>生产环境</code><span> 页面引入的所有 CSS 与 JS 文件都 </span><strong><span>必须</span></strong><span> 是 </span><strong><span>压缩后</span></strong><span> 的文件。 </span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="版本管理"><center>版本管理</center></h2><hr><p>&nbsp;</p><p><span style="background:#fff9a0"><span>所有项目 </span><strong><span>必须</span></strong><span> 上传到公司 </span><code>git</code><span> 仓库中，多人协作时，</span><strong><span>必须</span></strong><span> 创建每个人所负责的功能 </span><code>分支</code><span>，</span><strong><span>不应该</span></strong><span> 在 </span><code>master</code><span> 分支下开发和提交。</span></span></p><p><span style="background:#fff9a0"><span>新添加的需求或功能更新 </span><strong><span>必须</span></strong><span> 创建分支，在分支下开发。</span></span></p><p><span style="background:#fff9a0"><span>每次更新，先在本地测试完成后，</span><strong><span>应该</span></strong><span> 先提交到 </span><code>git</code><span> ，再同步到测试服务器，</span><code>git</code><span> 代码版本 </span><strong><span>必须</span></strong><span> 与测试版本同步，</span><strong><span>测试版本永远领先于生产版本</span></strong><span>。</span></span></p><h2 id='敏感文件'><span>敏感文件</span></h2><hr /><p><span style="background:#fff9a0"><span>（如微信支付公钥） </span><strong><span>必须 </span></strong><span>单独存放文件夹，并且目录内创建 </span><code>!.gitignore</code><span> 文件，如果将敏感文件上传到仓库，所产生的安全问题将 </span><strong><em><span>追究责任到个人</span></em></strong><span> 。</span></span></p><h2 id='没必要的文件'><span>没必要的文件</span></h2><hr /><p><span style="background:#fff9a0"><strong><span>绝不</span></strong><span> 上传日志和用户图片、框架源码或其他扩展包到仓库，这些文件除了占用空间外并没有其他意义。</span></span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="开发约定"><center>开发约定</center></h2><hr><h2 id='测试环境'><span>测试环境</span></h2><hr /><p><span>测试环境网址统一为：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre>x</pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">test-项目缩写.公司域名.com</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>测试管理后台账号密码和登录地址统一为：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">test-项目缩写.公司域名.com/admin</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">admin</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">123123</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 67px;"></div><div class="CodeMirror-gutters" style="display: none; height: 67px;"></div></div></div></pre><p><span>前端测试账号密码统一为：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">123123</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><h2 id='统一进度'><span>统一进度</span></h2><hr /><p><span style="background:#fff9a0"><span>新增需求或修改时，不论 </span><code>前端</code><span> 或 </span><code>后端</code><span> 哪边先完成工作，</span><strong><span>绝不</span></strong><span> 提交代码到正式环境，</span><strong><span>必须</span></strong><span> 等待整体功能完成，经过测试环境后，先由后端提交正式环境，然后告知前端提交正式环境。</span></span></p><h3 id='修改和新增'><strong><span>修改和新增</span></strong></h3><hr /><ol start='' ><li><span style="background:#fff9a0"><span>新增需求或修改时，</span><strong><span>必须</span></strong><span> 提交当前 </span><code>git</code><span> 版本，如果新增需求较多，</span><strong><span>应该</span></strong><span> 按完成的模块分次提交 </span><code>git</code><span>。</span></span></li><li><span style="background:#fff9a0"><span>如果需要在正式环境测试流程，测试商品或流程 </span><strong><span>必须</span></strong><span> 写明测试，避免客户混淆产生不必要的问题。</span></span></li><li><span style="background:#fff9a0"><span>不影响客户使用的修改，</span><strong><span>应该</span></strong><span> 使用热更新，其他需要停止服务器运行的修改 </span><strong><span>必须</span></strong><span> 提前告知。</span></span></li><li><span style="background:#fff9a0"><span>任何修改和测试，都 </span><strong><span>必须</span></strong><span> 先通过测试环境，如果连接正式环境进行功能修改和测试，一旦发生问题，</span><strong><em><span>追究责任到个人</span></em></strong><span>。</span></span></li></ol><h3 id='数据迁移'><strong><span>数据迁移</span></strong></h3><hr /><ol start='' ><li><span style="background:#fff9a0"><span>在数据迁移时，需要先在测试环境进行充分测试，确保结果无误后再到正式环境执行。</span><strong><span>应该</span></strong><span> 对比表字段，类型等信息，避免出现错误。</span></span></li><li><span style="background:#fff9a0"><strong><span>必须</span></strong><span> 保证 </span><code>测试环境</code><span> 与 </span><code>正式环境</code><span> 数据库表字段同步。</span></span></li></ol><h3 id='禁止修改框架源码'><span style="background:#fff9a0"><strong><span>禁止修改框架源码</span></strong></span></h3><hr /><p><span>如今我们很少写原生代码，开发流程都是使用 </span><code>组件</code><span> + </span><code>框架</code><span> 来完成，这给我们开发提供了很大便利，但这些是不纳入 </span><code>版本管理</code><span> 中的，因为我们在部署项目时可以使用 </span><code>composer</code><span> 来管理这些扩展。</span></p><p><span>如果你的开发过程觉得某些地方必须要修改 </span><code>框架</code><span> 或 </span><code>组件</code><span> 源码，那么有以下 2 种原因：</span></p><ol start='' ><li><span>你的使用方法错误，或理解错误</span></li><li><span>没有仔细阅读官方文档</span></li></ol><p><span>别怀疑，错的一定是你，不会是 </span><code>组件</code><span> 或 </span><code>框架</code><span> 的作者，除非你使用了很冷门或早已放弃维护的扩展，如果是那样就要反思你自己的 </span><code>技术栈</code><span> 是不是落伍了。</span></p><h3 id='前端开发约定'><strong><span>前端开发约定</span></strong></h3><hr /><p><span>任何 </span><code>可变</code><span> 参数，前端都 </span><strong><span>应该</span></strong><span> 向后端接口请求后进行数据绑定，文件夹和文件不能包含中文。</span></p><p><span>所有页面变量 </span><strong><span>必须</span></strong><span> 设定初始值。</span></p><p><span>调试数据 </span><code>console</code><span> 发布生产环境 </span><strong><span>必须</span></strong><span> 删除，或通过全局变量判断是在否在 </span><code>测试环境</code><span> 中。</span></p><p><span>使用 </span><code>webpack</code><span> 打包时，</span><strong><span>绝不</span></strong><span> 打包开发使用组件，只打包项目运行依赖组件，同样，在 </span><code>npm</code><span> 安装开发组件时 </span><strong><span>必须</span></strong><span> 使用 </span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">npm install --save-dev</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>生产依赖组件 </span><strong><span>必须</span></strong><span> 使用</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang=""><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">npm install --save</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>APP开发除活动页或一次性使用页面之外 </span><strong><span>不应该</span></strong><span> 使用 </span><code>H5</code><span> 页面展现与交互，如果有特殊情况，应团队讨论后决定是否使用。</span></p><p><span>对于涉及到用户金额，下单，秒杀等 </span><strong><span>非幂等性</span></strong><span> 接口，前端 </span><strong><span>必须</span></strong><span> 添加防抖功能，避免重复点击产生多条请求。</span></p><h3 id='后端开发约定'><strong><span>后端开发约定</span></strong></h3><hr /><p><span style="background:#fff9a0"><span>当前端请求列表时，必须添加 </span><code>LIMIT</code><span> ，并且最大返回条数 </span><strong><span>绝不</span></strong><span> 超过 30，类似 </span><code>users</code><span>，</span><code>news</code><span> 这种数据表 </span><strong><span>绝不</span></strong><span> 使用 </span><code>all()</code><span> 方法返回所有记录，这样将来如果数据量大，卡顿是必然的。</span></span></p><p><span style="background:#fff9a0"><code>Android</code><span> 和 </span><code>IOS</code><span> 对 </span><code>null</code><span> 处理不友好，所有接口返回数据必须对 </span><code>null</code><span> 进行单独处理。</span></span></p><p><span>错误示例：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="json"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="json"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-property">data</span>:{</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-property">list</span>: <span class="cm-atom">null</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 112px;"></div><div class="CodeMirror-gutters" style="display: none; height: 112px;"></div></div></div></pre><p><span>正确示例：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="json"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="json"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-property">data</span>:{</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-property">list</span>: <span class="cm-string">''</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 112px;"></div><div class="CodeMirror-gutters" style="display: none; height: 112px;"></div></div></div></pre><p><span style="background:#fff9a0"><strong><span>绝不</span></strong><span> 使用 </span><code>update($request-&gt;all())</code><span> 类似方法进行批量更新，</span><strong><span>必须</span></strong><span> 筛选前端提交的参数。</span></span></p><p><span>原因 </span><a href="#批量赋值"><span>「批量赋值」</span></a></p><h3 id='敏感数据'><strong><span>敏感数据</span></strong></h3><p><span>对于敏感数据，例如 </span><code>用户余额</code><span> 这样的字段，</span><strong><span>应该</span></strong><span> 使用 </span><code>观察者</code><span> 或 </span><code>触发器</code><span> 进行监听，在发生变化时，及时保留记录。不论是管理后台手动添加修改还是用户购买行为，这样做可以避免出错时与客户的纠纷。</span></p><h2 id='版本更新'><span>版本更新</span></h2><hr /><p><span style="background:#fff9a0"><span>在做添加功能或修复重要 BUG 这种会影响用户使用的修改后，为了避免不必要的误会，APP端 </span><strong><span>必须</span></strong><span> 使用强制更新，小程序 </span><strong><span>必须</span></strong><span> 提醒用户重新删除小程序重新进入。</span></span></p><h2 id='交付'><span>交付</span></h2><hr /><p><span>产品上线后，所有 </span><code>测试</code><span> 数据 </span><strong><span>必须</span></strong><span> 删除，给客户的数据库 </span><strong><span>必须</span></strong><span> 是清空的，所有表的 </span><code>id</code><span> 从 1 开始自增，管理后台登录地址 </span><strong><span>必须</span></strong><span> 使用 </span><code>登录地址</code><span> + </span><code>随机字符串</code><span> 拼接，避免被 </span><code>有心人</code><span> 轻易猜到。</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="json"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="json"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 错误</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{<span class="cm-property">项目网址</span>}<span class="cm-operator">/</span><span class="cm-variable">admin</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 正确</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{<span class="cm-property">项目网址</span>}<span class="cm-operator">/</span><span class="cm-variable">admin_FwTZXCB6bG9o7FNU</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 90px;"></div><div class="CodeMirror-gutters" style="display: none; height: 90px;"></div></div></div></pre><p><strong><span>必须</span></strong><span> 修改宝塔默认 </span><code>8888</code><span> 端口，</span><strong><span>应该</span></strong><span> 关闭 </span><code>SSH</code><span> 密码登录，避免被爆破，或使用更安全的 </span><code>PEM</code><span> 证书登录。</span></p><p><span>主域名 </span><strong><span>必须</span></strong><span> 解析 </span><code>WWW</code><span> 和 没有 </span><code>WWW</code><span> ，确保 </span><code>www.baidu.com</code><span> 和 </span><code>baidu.com</code><span> 都可以访问。</span></p><p><span>管理后台 </span><strong><span>必须</span></strong><span> 开启操作记录日志功能，</span><strong><span>必须</span></strong><span> 使用富文本编辑器，选择的编辑器功能需要齐全一些，至少要包含 </span><strong><span>视频</span></strong><span>， </span><strong><span>音频</span></strong><span>， </span><strong><span>图片编辑</span></strong><span>，</span><strong><span>多图上传</span></strong><span> 等功能，例如百度的 </span><code>ueditor</code><span>，或扩展性强的 </span><code>wangEditor</code><span> 等。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="API文档规范"><center>API文档规范</center></h2><hr>
<h3><strong>传参示例</strong></h3><hr><p><span style="background:#fff9a0"><span>每个接口都 </span><strong><span>必须</span></strong><span> 对传参，响应结果有详细说明，</span><strong><span>必须</span></strong><span> 添加示例。</span></span></p><p><span>传参示例：</span></p><p><img src="assets/images/manual/api1.png" alt="2" style="zoom:60%;" /></p><p><span>响应结果示例：</span></p><p><img src="assets/images/manual/api2.png" alt="2" style="zoom:60%;" /></p><p><span style="background:#fff9a0"><span>当接口迭代，导致接口「参数变化」</span><strong><span>必须</span></strong><span> 及时通知相关前端人员进行更新</span></span><span>，避免发生错误，如果参数发生变化，</span><strong><span>应该</span></strong><span> 对老接口做数据兼容处理。</span></p><h3><strong>接口节流</strong></h3><hr><p><span>所有对外访问的 API 接口 </span><strong><span>应该</span></strong><span> 做节流处理，普通访问 </span><strong><span>应该</span></strong><span> 限定单用户最多「60次 / 分钟」，</span><code>用户登录</code><span>，</span><code>请求短信验证码</code><span> ，等接口 </span><strong><span>应该</span></strong><span> 限定单用户最多「10次 / 分钟」。</span></p><h3><strong>用户验证</strong></h3><hr><p><span>所有 </span><code>当前登录</code><span> 用户相关的 </span><code>数据交互</code><span>，</span><strong><span>必须</span></strong><span> 使用 </span><code>jwt</code><span> 或 </span><code>passport</code><span> 方式进行授权验证。</span></p><h3><strong>使用正确的HTTP状态码</strong></h3><hr><p><span>正常的 API 接口请求，HTTP 状态码小于 </span><strong><span>400</span></strong><span> 都视为成功请求，</span><span style="background:#fff9a0"><strong><span>自定义状态码</span></strong><span> 需要前后端提前约定，</span><strong><span>绝不</span></strong><span> 使用 HTTP 状态码作为 </span><strong><span>自定义</span></strong><span> 状态码。</span></span></p><p><span>错误示范：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="json"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="json"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{ <span class="cm-tab" role="presentation" cm-text="	">  </span><span class="cm-property">HTTP</span> <span class="cm-variable">Status</span> <span class="cm-operator">=</span> <span class="cm-number">200</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-string">"code"</span>:<span class="cm-number">200</span> &nbsp;<span class="cm-comment">// 自定义状态码</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-string">"data"</span>:{</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-meta">...</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 134px;"></div><div class="CodeMirror-gutters" style="display: none; height: 134px;"></div></div></div></pre><p><span>正确示范：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="json"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="json"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre>x</pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">{ <span class="cm-tab" role="presentation" cm-text="	">  </span><span class="cm-property">HTTP</span> <span class="cm-variable">Status</span> <span class="cm-operator">=</span> <span class="cm-number">200</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-string">"code"</span>:<span class="cm-variable">XXX001</span> &nbsp;<span class="cm-comment">// 自定义状态码</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-string">"data"</span>:{</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-meta">...</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 134px;"></div><div class="CodeMirror-gutters" style="display: none; height: 134px;"></div></div></div></pre><p><span>推荐阅读：</span><a href='https://blog.csdn.net/hedeqiang9436/article/details/106498614'><span>正确使用状态码</span></a></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="前端编码规范"><center>前端编码规范</center></h2><hr><p>&nbsp;</p><p><span>前端代码规范相对复杂，整理为以下链接：</span></p><ol start='' ><li><a href='https://www.w3cschool.cn/bdl2e3/9tsv12j2.html'><span>一般规范</span></a></li><li><a href='https://www.w3cschool.cn/bdl2e3/h36912j3.html'><span>HTML规范</span></a></li><li><a href='https://www.w3cschool.cn/bdl2e3/arcv12j4.html'><span>Javascript规范</span></a></li><li><a href='https://www.w3cschool.cn/bdl2e3/9t6n12j5.html'><span>CSS 和 Sass(SCSS)规范</span></a></li></ol><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="后端编码规范"><center>后端编码规范</center></h2><hr>
<h3><strong>代码风格</strong></h3><hr><p><span>代码风格 </span><strong><span>必须</span></strong><span> 遵守 PSR-1 和 PSR-2 规范</span></p><p><span>参考链接 </span><a href='http://www.kkh86.com/it/code-standard/guide-psr-2.html'><span>PSR标准</span></a></p><h3><strong>路由定义</strong></h3><hr><p><span>路由器要保持干净整洁，</span><strong><span>绝不</span></strong><span> 放置除路由配置以外的其他程序逻辑。</span></p><p><span>设计路由 </span><strong><span>应该</span></strong><span> 参考 </span><a href='https://www.zhihu.com/question/28557115'><code>Restful</code></a><span> 风格。</span></p><p><span>简单解释 </span><code>Restful</code><span> 风格路由：</span></p><ol start='' ><li><span>看 </span><code>Url</code><span> 就知道要什么</span></li><li><span>看 </span><code>Http method</code><span> 就知道干什么</span></li><li><span>看 </span><code>Http status code</code><span> 就知道结果如何</span></li></ol><p><span>扩展阅读：</span><a href='http://www.ruanyifeng.com/blog/2011/09/restful.html'><span>理解RESTful架构</span></a></p><h3><strong style="background:#fff9a0">数据填充</strong></h3><hr><p><span>在开发某个 </span><code>功能</code><span> 的时候，</span><strong><span>应该</span></strong><span> 先写 </span><code>数据填充</code><span> 脚本，并且先写好数据返回接口给前端，这样前后端可以双线进行开发，不需要等待后端开发写好业务逻辑后再去绑定数据，</span><strong><span>不应该</span></strong><span> 去数据库手动添加数据条目供前端绑定。</span></p><p><strong><span>原因有 2 点：</span></strong></p><ol start='' ><li><span>效率低，因为数据库每个字段都有类型或必填限制</span></li><li><span>不能满足测试需求，手动添加数据，数量少，不能满足前端分页等页面效果的展现</span></li></ol><h3><strong>代码注释</strong></h3><hr><p><span style="background:#fff9a0"><span>业务逻辑方法添加 </span><strong><span>应该</span></strong><span> 有注释，越复杂的逻辑方法越应该书写详细的注释：</span></span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">/**</span></span></pre></div><div class="" style="position: relative;"><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* 说明该方法的用途</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param float $balance 余额</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param int $expired_integral 过期积分</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param json $customer 消费者信息</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @return array</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">**/</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-def">execExpiredIntegral</span>(<span class="cm-variable-2">$balance</span>, <span class="cm-variable-2">$expired_integral</span>, <span class="cm-variable-2">$customer</span>){</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-comment">// todo</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 224px;"></div><div class="CodeMirror-gutters" style="display: none; height: 224px;"></div></div></div></pre><p><span>多人协作时，复杂的业务逻辑方法 </span><strong><span>应该</span></strong><span> 在注释中表明开发者，这样遇到问题方便及时定位到开发人员进行修改。</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">/** </span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* Developer 张三</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param float $balance 余额</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param int $expired_integral 过期积分</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @param json $customer 消费者信息</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> <span class="cm-comment">* @return array</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">**/</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-def">execExpiredIntegral</span>(<span class="cm-variable-2">$balance</span>, <span class="cm-variable-2">$expired_integral</span>, <span class="cm-variable-2">$customer</span>){</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-comment">// todo</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 224px;"></div><div class="CodeMirror-gutters" style="display: none; height: 224px;"></div></div></div></pre><p>&nbsp;</p><h3><strong>数据验证</strong></h3><hr><p><span style="background:#fff9a0"><span>后端验证规则 </span><strong><span>必须</span></strong><span> 与前端一致，不可相信前端接口提交的数据，</span><strong><span>必须</span></strong><span> 进行二次验证，尤其是入库操作。</span></span></p><p><span>如果验证失败，返回信息应该与前端相同。</span></p><h3><strong>数据提交</strong></h3><hr><p><span style="background:#fff9a0"><span>复杂或重要的业务逻辑中，需要多次入库或查询，</span><strong><span>必须</span></strong><span> 将数据库交互放在事务块代码中，并且在 </span><code>try</code><span> ，</span><code>catch</code><span> 中进行。</span></span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">public</span> <span class="cm-keyword">function</span> <span class="cm-def">depositBalance</span>(<span class="cm-variable-2">$id</span>, <span class="cm-variable-2">$number</span>){</span></pre></div><div class="" style="position: relative;"><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-keyword">try</span>{</span></pre></div><div class="" style="position: relative;"><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-variable">DB</span>::<span class="cm-variable">DB</span>::<span class="cm-variable">startTrans</span>();</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-variable">DB</span>::<span class="cm-variable">table</span>(<span class="cm-string">'user_balance'</span>)<span class="cm-operator">-&gt;</span><span class="cm-variable">where</span>(<span class="cm-string">'id'</span>)<span class="cm-operator">-&gt;</span><span class="cm-variable">inrc</span>(<span class="cm-string">'balance'</span>, <span class="cm-variable-2">$number</span>);</span></pre><div class="" style="position: relative;"><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-variable">DB</span>::<span class="cm-variable">commit</span>();</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>} <span class="cm-keyword">catch</span> (<span class="cm-variable">\Exception</span> <span class="cm-variable-2">$e</span>) {</span></pre><div class="" style="position: relative;"><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-variable">DB</span>::<span class="cm-variable">rollback</span>();</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-tab" role="presentation" cm-text="	">    </span><span class="cm-variable">Log</span>::<span class="cm-variable">error</span>(<span class="cm-number">500</span>, <span class="cm-variable-2">$e</span><span class="cm-operator">-&gt;</span><span class="cm-variable">message</span>()); <span class="cm-comment">// 记录错误到日志或直接打印它</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tab" role="presentation" cm-text="	">    </span>}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 224px;"></div><div class="CodeMirror-gutters" style="display: none; height: 224px;"></div></div></div></pre><h3><strong>数据库操作规范</strong></h3><hr><p><span>开发人员 </span><strong><span>应该</span></strong><span> 尽量避免直接操作数据库，因为在多人协作的测试环境，数据库的表和数据随时会被其他同事覆写。</span></p><p><span>所有对数据库表的 </span><code>创建</code><span> 和 </span><code>迁移</code><span> 都应该在框架中进行，并且创建迁移文件，纳入版本管理中。</span></p><p><span style="background:#fff9a0"><span>所有数据库表的字段 </span><strong><span>应该</span></strong><span> 填写注释，方便其他开发人员查看</span></span><span>，当然，</span><code>id</code><span>，</span><code>user_name</code><span> 这种常见字段不需要填写注释，与业务逻辑相关的字段 </span><strong><span>必须</span></strong><span> 填写注释。</span></p><p><span style="background:#fff9a0"><strong><span>绝不</span></strong><span> 在代码中使用 for 循环查询数据库，所有数据结果应该一次查询</span></span><span>，如果查询数据量较大，应该在代码层面进行优化，分批次处理。</span></p><h3><strong>数据库表关联</strong></h3><hr><p><span>假如现在有 </span><code>user</code><span> ，</span><code>user_balance</code><span>，</span><code>user_address</code><span> 3个表，</span><code>user</code><span> 表中的每条数据与 </span><code>user_balance</code><span> 和 </span><code>user_address</code><span> 表是一对一关系，当 </span><code>user</code><span> 表的数据被删除时，为了避免数据冗余与数据损坏，相对应的其他两个表数据也应该被一起删除。</span></p><p><span>所以在创建 </span><code>一对一</code><span> 或 </span><code>一对多</code><span> 表时，要为每个表添加 </span><code>外键约束</code><span>。</span></p><h3><strong>生产环境的数据库</strong></h3><hr><p><span style="background:#fff9a0"><span>意外情况总是不可避免的，我们需要去操作 </span><code>生产环境</code><span> 的数据库，在生产环境执行 </span><code>update</code><span> 和 </span><code>delete</code><span> 语句时，</span><strong><span>必须</span></strong><span> 添加 </span><code>LIMIT</code><span> 或 </span><code>WHERE</code><span> 约束。</span></span></p><p><span style="background:#fff9a0"><span>生产环境数据库 </span><strong><span>必须</span></strong><span> 添加定时任务在每天 </span><strong><span>02:00 ~ 4:00</span></strong><span> 之间进行备份。</span></span></p><p><span>与数据库相关的定时任务都 </span><strong><span>应该</span></strong><span> 选择在午夜时间执行。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="二次开发项目"><center>二次开发项目</center></h2><hr><p><span>对待二次开发的项目，我们要想到一个问题，这个项目之前也是网络公司开发的，为什么会到我们手里？</span></p><p><span>对！你想的没错，这样的项目通常都是客户主动或被动的选择了放弃与之前公司的合作，选择了我们。但任何公司都不愿意失去客户，通常他们是不愿意交付源码和开发文档的，所以我们在接手时通常都会发现它是 </span><strong><em><span>残缺不全</span></em></strong><span> 的。</span></p><p><span>尽管对接时，有些项目是正常运行的。但阅读源码你会发现，这些项目要么就是被删掉了注释，要么就是部署文件不全或者有流程没有形成文档，而无从下手。</span></p><p><span>所以对待这样的项目我们应该保持</span><span style="background:#fff9a0"><span> 能运行就不要动 ，能添加就不修改 </span></span><span>的态度，在二次开发的过程中，我们大多时候都是从问题出发，又因为对整体业务逻辑的不了解，经常有 </span><code>管中窥豹</code><span> 的感觉。</span></p><p><span>这种情况下，我们在原有逻辑上进行修改时，</span><span style="background:#fff9a0"><strong><span>应该</span></strong><span> 先试着把修改的地方抽离成代码块</span></span><span>，在单独的方法中进行修改与测试，并且修改过程中，</span><span style="background:#fff9a0"><strong><span>不应该</span></strong><span> 删除原有的参数和方法</span></span><span>，因为不知道哪里会用到它，</span><span style="background:#fff9a0"><strong><span>应该</span></strong><span> 将新增的逻辑添加到独立方法中。</span></span></p><h3 id='写下注释记录文档'><strong><span>写下注释，记录文档</span></strong></h3><hr /><p><span>通常情况下，二次开发的项目我们也需要长期维护，在阅读源码时，要写下对方法和业务逻辑的理解，避免以后维护的时候 </span><code>换人</code><span> 导致 </span><code>开发资源</code><span> 的浪费。</span></p><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="测试反馈"><center>测试反馈</center></h2><hr><p><span>开发人员应该严谨对待功能的修改和开发，哪怕是只修改了一个变量，也 </span><strong><span>必须</span></strong><span> 在自己测试过之后，再交付给测试人员，并说明业务逻辑。</span></p><p><span>测试人员在反馈 </span><code>缺陷</code><span> 时，只需要考虑一点：</span></p><blockquote><p><span style="background:#fff9a0"><strong><span>如何让开发人员复现你所发现的问题？</span></strong></span></p></blockquote><p><span>而不是只把问题抛出来，这样是没有意义的。</span></p><hr /><h3><strong>以下是 缺陷 反馈模板：</strong></h3><ol start='' ><li><p><span>环境：</span><code>生产</code><span> 或 </span><code>测试</code></p></li><li><p><span>来源：需求问题 / 设计问题 / 编码问题 / 其他</span></p></li><li><p><span>操作步骤：</span></p><ol start='' ><li><span>进入应用并登录</span></li><li><span>购买商品</span></li><li><span>取消购买，再次添加购物车</span></li></ol></li><li><p><span>发现问题：</span><font color="#f00"><span>购物车出现2件相同商品，但是操作只添加了1件</span></font></p></li><li><p><span>是否有错误信息：如果有附上截图</span></p></li><li><p><span>是否重现：是 / 否</span></p></li><li><p><span>重现频率：小概率 / 经常 / 必然</span></p></li><li><p><span>严重性：致命 / 严重 / 一般 / 微小 / 建议 (A, B, C, D, E)</span></p></li><li><p><span>优先级：高 / 中 / 低</span></p></li><li><p><span>详细描述：如果你觉得截图和步骤无法表述清楚</span></p></li></ol><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="安全实践"><center>安全实践</center></h2><hr><p>&nbsp;</p><p><span>没有绝对安全，只有相对安全。如今市面的主流框架对安全已经做得得很优秀了，不过作为开发者，我们要在日常开发中对「安全」需怀着敬畏之心，积极培养自己的安全意识。</span></p><h3><strong>关闭 DEBUG</strong></h3><hr><p><span>不论是什么框架，都不应该在生产环境开启 </span><code>DEBUG</code><span> 模式，一旦会暴露很多可能被和黑客利用的服务器信息。</span></p><h3><strong>XSS</strong></h3><hr><p><span>跨站脚本攻击（cross-site scripting，简称 XSS），具体危害体现在黑客能控制你网站页面，包括使用 JS 盗取 Cookie 等，关于 XSS 的介绍请前往 </span><a href='https://blog.csdn.net/gl1987807/article/details/27046623'><span>跨站点脚本攻击深入解析</span></a></p><p><span>在页面渲染富文本时，</span><strong><span>必须</span></strong><span> 限定允许渲染的标签，或使用过滤插件。</span></p><p><span>以下是安全的 HTML标签 和 CSS 属性：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-string">'HTML.Allowed'</span> <span class="cm-operator">=&gt;</span> <span class="cm-string">'div,em,a[href|title|style],ul,ol,li,p[style],br'</span>,</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-string">'CSS.AllowedProperties'</span> &nbsp; &nbsp;<span class="cm-operator">=&gt;</span> <span class="cm-string">'font,font-size,font-weight,font-style,font-family'</span>,</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 45px;"></div><div class="CodeMirror-gutters" style="display: none; height: 45px;"></div></div></div></pre><h3><strong>SQL注入</strong></h3><hr><p><span>现在市面主流框架都是使用的 ORM 大多都是 </span><code>Eloquent</code><span> ，它是基于 PHP 的 PDO，可以防止绝大多数的SQL注入。</span></p><p><span>但是当我们使用 </span><code>DB::raw</code><span> 原生 SQL 语句的时候，</span><strong><span>必须</span></strong><span> 使用数据绑定。 </span></p><p><span>错误的做法：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">DB</span>::<span class="cm-variable">select</span>(<span class="cm-variable">DB</span>::<span class="cm-variable">raw</span>(<span class="cm-string">"SELECT * FROM users WHERE name ='</span><span class="cm-variable-2">$name</span><span class="cm-string">' and password = '</span><span class="cm-variable-2">$password</span><span class="cm-string">'"</span>));</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>正确的做法：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">DB</span>::<span class="cm-variable">select</span>(</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">DB</span>::<span class="cm-variable">raw</span>(<span class="cm-string">"SELECT * FROM users WHERE name =:name and password = :password"</span>),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;  [</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-string">'name'</span> <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$name</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-string">'password'</span> <span class="cm-operator">=&gt;</span> <span class="cm-variable-2">$password</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;  ]</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">);</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 157px;"></div><div class="CodeMirror-gutters" style="display: none; height: 157px;"></div></div></div></pre><h3 id="批量赋值"><strong>批量赋值</strong></h3><hr><p><span>批量赋值安全威胁，指的是用户可更新本来不应有权限更新的字段。举例，</span><code>users</code><span> 表里的 </span><code>is_admin</code><span> 字段是用来标识用户「是否是管理员」，某不怀好意的用户，更改了「修改个人资料」的表单，增加了一个字段：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="html"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="html"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">input</span> <span class="cm-attribute">name</span>=<span class="cm-string">"is_admin"</span> <span class="cm-attribute">value</span>=<span class="cm-string">"1"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>这个时候如果你更新代码如下：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">Auth</span>::<span class="cm-variable">user</span>()<span class="cm-operator">-&gt;</span><span class="cm-variable">update</span>(<span class="cm-variable">Request</span>::<span class="cm-variable">all</span>());</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>此用户将获取到管理员权限。</span></p><p><span>有很多种方法来避免这种情况的发生，以 </span><code>laravel</code><span> 框架为例，最简单的方法就是在 </span><code>user</code><span> 模型中 使用 </span><code>$fillable</code><span> 来过滤掉请求时提交的 </span><code>is_admin</code><span> 参数</span></p><h3><strong>CSRF</strong></h3><hr><p><span>CSRF 跨站请求伪造是 Web 应用中最常见的安全威胁之一，具体请见 </span><a href='https://zh.wikipedia.org/wiki/跨站请求伪造'><span>Wiki - 跨站请求伪造</span></a><span>。</span></p><p><span>现在主流的框架都是有 </span><code>CSRF</code><span> 验证，原理是对所有「非幂等的请求」需要验证表单中的 </span><strong><span>token</span></strong><span>，需要开发者做的，是区分清楚什么时候该使用『非幂等的请求』。</span></p><blockquote><p><span>幂等请求指的是：’HEAD’, ‘GET’, ‘OPTIONS’，既无论你执行多少次重复的操作都不会给资源造成变更。</span></p></blockquote><h3><strong>时序攻击</strong></h3><hr><p><span>时序攻击常见于 </span><code>API</code><span> 接口中，因为接口是对外开放的，任何人都可以去访问它，在我们验证 </span><code>用户登录</code><span> 或 </span><code>手机验证码</code><span> 时，黑客可以通过接口响应时间长短的方式来推算出正确的值。</span></p><p><span>举例，比如我们这样来比较两个字符串是否相等：</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable-2">$verifyData</span>[<span class="cm-string">'code'</span>] <span class="cm-operator">==</span> <span class="cm-variable-2">$request</span><span class="cm-operator">-&gt;</span><span class="cm-variable">verification_code</span></span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><p><span>进行比较，那么两个字符串是从第一位开始逐一进行比较的，发现不同就立即返回 false，那么通过计算返回的速度就知道了大概是哪一位开始不同的，这样就实现了电影中经常出现的按位破解密码的场景。</span></p><p><span>要避免这种情况也很简单，使用 </span><code>hash_equals</code><span> 比较两个字符串，无论字符串是否相等，函数的时间消耗是恒定的，这样可以有效的防止时序攻击。</span></p><pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="php"><div class="CodeMirror cm-s-inner CodeMirror-wrap" lang="php"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 9.2px; left: 8px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">hash_equals</span>(<span class="cm-variable-2">$verifyData</span>[<span class="cm-string">'code'</span>], <span class="cm-variable-2">$request</span><span class="cm-operator">-&gt;</span><span class="cm-variable">verification_code</span>)</span></pre></div></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 22px;"></div><div class="CodeMirror-gutters" style="display: none; height: 22px;"></div></div></div></pre><div style="page-break-after: always;"></div>
<div>
    <p align="right"><font color="#999" style="font-size:12px; padding-right:8px"><a href="#目录">目录</a></font><font color="#999" style="font-size:12px">ZH网络科技</font></p>
</div><h2 id="推荐阅读"><center>推荐阅读</center></h2><hr><p>&nbsp;</p><p><span>向别人提出问题的正确姿势：</span></p><p><span>	</span><a href='https://www.jianshu.com/p/1b4ac73d4966'><span>《提问的智慧》</span></a></p><p><span>别再使用 </span><code>office word</code><span> 写文档了：</span></p><blockquote><p><span>很多公司部门喜欢使用 office 套件，但是对于科技公司内部沟通而言，它实在是太笨重了，一点也不 GEEK， 来学学 Markdown 语法吧，保证你会爱上它的。</span></p></blockquote><p><span>	</span><a href='https://www.runoob.com/markdown/md-tutorial.html'><span>《Markdown 语法教程》</span></a></p><p><span>如何在你的工作中找到幸福感：</span></p><blockquote><p><span>工作是人生的道场，是通往幸福道路的阶梯</span></p></blockquote><p><span>	</span><a href='http://yuedu.163.com/book_reader/a85f5b82d68748e28bf529ee9d495135_4'><span>《干法》稻盛和夫</span></a></p><hr /><center>年轻时的苦难是一种财富，花钱都买不到。</center><p><span>																															</span><em><span>鹿儿岛谚语</span></em></p></div></div>
</body>
</html>